﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
using dotNetMembership=System.Web.Security.Membership;
using dotNetRoles=System.Web.Security.Roles;
using Binding=System.Web.Mvc.BindingHelperExtensions;
using System.Web.Routing;
using StarterKits.Mvc.Filters.FormsAuthentication;

namespace StarterKits.Mvc.Membership.Controllers
{
	public abstract class BaseFormsAuthenticationAdministrationController : Controller
	{

		#region Utility & protected methods.

		[NonAction]
		public static void RegisterDefaultRoutes( RouteCollection routes, string baseUrl, string c, string extension )
		{
			routes.MapRoute( "FormsAuthenticationAdministration_UserDetails",
								baseUrl + ( extension ?? "" ) + "/User/{id}",
								new { controller = c, action = "UserDetails", id = (Guid)Guid.Empty } );
			routes.MapRoute( "FormsAuthenticationAdministration_UserList",
								baseUrl + ( extension ?? "" ) + "/Users/{pageIndex}/{pageSize}",
								new { controller = c, action = "Users", pageIndex = 0, pageSize = 25 } );
			routes.MapRoute( "FormsAuthenticationAdministration_Default",
								baseUrl + ( extension ?? "" ) + "/{action}/{id}",
								new { controller = c, action = "Index", id = "" } );
		}

		protected ActionResult RedirectToUserPage( MembershipUser user )
		{
			RouteValueDictionary rvd = new RouteValueDictionary( new
			{
				controller = this.ControllerContext.RouteData.Values["controller"],
				action = "UserDetails",
				id = (Guid)user.ProviderUserKey
			} );
			return RedirectToRoute( rvd );
		}

		protected List<T> ToList<T>( IEnumerable source )
		{
			List<T> list = new List<T>();
			foreach( T t in source )
				list.Add( t );
			return list;
		}
		protected List<TTo> ToList<TFrom, TTo>( IEnumerable source, Func<TFrom, TTo> convert )
		{
			List<TTo> list = new List<TTo>();
			foreach( TFrom t in source )
				list.Add( convert( t ) );
			return list;
		}

		#endregion

		#region Role management.

		[NonAction]
		public virtual void OnBeforeCreateRole( string role ) { }
		[NonAction]
		public virtual void OnAfterCreateRole( string role ) { }
		[NonAction]
		public virtual void OnBeforeDeleteRole( string id ) { }
		[NonAction]
		public virtual void OnAfterDeleteRole( string id ) { }
		[NonAction]
		public virtual void OnBeforeAddUserToRole( string userName, string roleName ) { }
		[NonAction]
		public virtual void OnAfterAddUserToRole( string userName, string roleName ) { }
		[NonAction]
		public virtual void OnBeforeRemoveUserFromRole( string userName, string roleName ) { }
		[NonAction]
		public virtual void OnAfterRemoveUserFromRole( string userName, string roleName ) { }

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult CreateRole( string role )
		{
			OnBeforeCreateRole( role );
			dotNetRoles.CreateRole( role );
			OnAfterCreateRole( role );
			return RedirectToAction( "Roles" );
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult DeleteRole( string id )
		{
			OnBeforeDeleteRole( id );
			dotNetRoles.DeleteRole( id );
			OnAfterDeleteRole( id );
			return RedirectToAction( "Roles" );
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult Roles()
		{
			string[] roles = dotNetRoles.GetAllRoles();
			return View( roles.OrderBy( x => x ).ToList() );
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult Role( string id )
		{
			string[] userNames = dotNetRoles.GetUsersInRole( id );
			MembershipRoleViewData role = new MembershipRoleViewData
			{
				Name = id,
				Users = ToList<string,MembershipUser>( userNames, u=> dotNetMembership.GetUser(u) )
			};
			return View( "UsersInRole", role );
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult AddUserToRole( Guid userId, string roleName )
		{
			MembershipUser user = dotNetMembership.GetUser( userId );
			OnBeforeAddUserToRole( user.UserName, roleName );
			dotNetRoles.AddUserToRole( user.UserName, roleName );
			OnAfterAddUserToRole( user.UserName, roleName );
			return RedirectToUserPage(user);
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult RemoveUserFromRole( Guid userId, string roleName )
		{
			MembershipUser user = dotNetMembership.GetUser(userId);
			OnBeforeRemoveUserFromRole( user.UserName, roleName );
			dotNetRoles.RemoveUserFromRole( user.UserName, roleName );
			OnAfterRemoveUserFromRole( user.UserName, roleName );
			return RedirectToUserPage(user);
		}

		#endregion

		#region List/display users.

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult Users( int pageIndex, int pageSize )
		{
			int totalRecords = 0;
			MembershipUserCollection members = dotNetMembership.GetAllUsers( pageIndex, pageSize, out totalRecords );
			return View( "ListUsers", ToList<MembershipUser>(members) );
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult UserDetails( Guid id )
		{
			MembershipUser user = dotNetMembership.GetUser(id);
			MembershipUserAndRolesViewData data = new MembershipUserAndRolesViewData
			{
				RolesEnabled = dotNetRoles.Enabled,
				RequiresQuestionAndAnswer = dotNetMembership.RequiresQuestionAndAnswer,
				User = user,
				AllRoles = dotNetRoles.GetAllRoles().OrderBy( x => x ).ToList(),
				UsersRoles = dotNetRoles.GetRolesForUser( user.UserName ).OrderBy( x => x ).ToList()
			};
			return View( "DisplayUser", data );
		}

		#endregion

		#region Edit user.

		[NonAction]
		public virtual void OnBeforeUpdateUser( string userName ) { }
		[NonAction]
		public virtual void OnAfterUpdateUser( string userName ) { }
		[NonAction]
		public virtual void OnBeforeUnlockUser( string id ) { }
		[NonAction]
		public virtual void OnAfterUnlockUser( string id ) { }

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult SaveExistingUser()
		{

			string userName = Request.Form["UserName"];

			OnBeforeUpdateUser( userName );

			MembershipUser user = dotNetMembership.GetUser( userName );

			Binding.UpdateFrom( user, Request.Form, "MembershipUser" );
			user.IsApproved = ( ( Request.Form["XMembershipUser.IsApproved"] ?? "" ).Trim().Equals( "on", StringComparison.CurrentCultureIgnoreCase ) );
			dotNetMembership.UpdateUser( user );

			OnAfterUpdateUser( userName );

			return RedirectToUserPage( user );

		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult UnlockUser( Guid id )
		{

			MembershipUser user = dotNetMembership.GetUser( id );

			OnBeforeUnlockUser( user.UserName );
			bool success = user.UnlockUser();
			OnAfterUnlockUser( user.UserName );

			return RedirectToUserPage( user );

		}

		#endregion

		#region User creation.

		[NonAction]
		public virtual void OnBeforeCreateUser( string userName, string emailAddress, string password, string passwordConfirm, string passwordQuestion, string passwordAnswer ) { }
		[NonAction]
		public virtual void OnAfterCreateUser( string userName, string emailAddress, string password, string passwordConfirm, string passwordQuestion, string passwordAnswer ) { }
		[NonAction]
		public virtual void OnCreateUserError( string userName, string emailAddress, string password, string passwordConfirm, string passwordQuestion, string passwordAnswer, string error ) { }

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult CreateUser()
		{
			ViewData["RequiresQuestionAndAnswer"] = dotNetMembership.RequiresQuestionAndAnswer;
			return View();
		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult SaveNewUser()
		{
			string userName = Request.Form["MembershipUser.UserName"];
			string password = Request.Form["MembershipUser.Password"];
			string passwordConfirm = Request.Form["MembershipUser.ConfirmPassword"];
			string email = Request.Form["MembershipUser.EmailAddress"];
			string pwdQuestion = Request.Form["MembershipUser.PasswordQuestion"];
			string pwdAnswer = Request.Form["MembershipUser.PasswordAnswer"];
			bool isApproved = ( ( Request.Form["MembershipUser.IsApproved"] ?? "" ).Trim().Equals( "on", StringComparison.CurrentCultureIgnoreCase ) );

			OnBeforeCreateUser( userName, email, password, passwordConfirm, pwdQuestion, pwdAnswer );
			MembershipCreateStatus status = MembershipCreateStatus.ProviderError;
			MembershipUser user = null;

			if( password == passwordConfirm )
			{
				Guid providerUserKey = AssociateNewUserToProviderUserKey( userName, email );
				if( providerUserKey == Guid.Empty )
					user = dotNetMembership.CreateUser( userName, password, email, pwdQuestion, pwdAnswer, isApproved, out status );
				else
					user = dotNetMembership.CreateUser( userName, password, email, pwdQuestion, pwdAnswer, isApproved, providerUserKey, out status );

				OnAfterCreateUser( userName, email, password, passwordConfirm, pwdQuestion, pwdAnswer );
			}
			else
				status = MembershipCreateStatus.InvalidPassword;

			if( status == MembershipCreateStatus.Success )
				return RedirectToUserPage( user );
			else
			{

				OnCreateUserError( userName, email, password, passwordConfirm, pwdQuestion, pwdAnswer, status.ToString() );

				TempData["MembershipCreateStatus"] = status;
				TempData["MembershipUser.UserName"] = userName;
				TempData["MembershipUser.EmailAddress"] = email;
				TempData["MembershipUser.PasswordQuestion"] = pwdQuestion;
				TempData["MembershipUser.PasswordAnswer"] = pwdAnswer;
				TempData["MembershipUser.IsApproved"] = isApproved;
				return RedirectToAction( "CreateUser" );

			}

		}
		protected virtual Guid AssociateNewUserToProviderUserKey( string userName, string email )
		{
			return Guid.Empty;
		}

		#endregion

		#region Password management.

		[NonAction]
		public virtual void OnBeforeResetPassword( string userName, string passwordAnswer ) { }
		[NonAction]
		public virtual void OnAfterResetPassword( string email, string userName, string newPassword ) { }
		[NonAction]
		public virtual void OnBeforePasswordChange( string userName, string currentPassword, string newPassword, string newPasswordConfirm ) { }
		[NonAction]
		public virtual void OnAfterPasswordChange( string userName, string currentPassword, string newPassword, string newPasswordConfirm ) { }

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult ChangePassword()
		{

			string userName = Request.Form["UserName"];
			string oldPwd = Request.Form["OldPassword"];
			string newPwd = Request.Form["NewPassword"];
			string newPwdConfirm = Request.Form["NewPasswordConfirm"];
			bool success = false;

			OnBeforePasswordChange( userName, oldPwd, newPwd, newPwdConfirm );

			MembershipUser user = dotNetMembership.GetUser( userName );
			if( newPwd == newPwdConfirm )
				success = user.ChangePassword( oldPwd, newPwd );
			else
				throw new MembershipPasswordException( "\"New Password\" does not match \"Confirm New Password\"." );

			OnAfterPasswordChange( userName, oldPwd, newPwd, newPwdConfirm );

			return RedirectToUserPage( user );

		}

		[RequiresRole( Role="Administrator" )]
		public virtual ActionResult ResetPassword()
		{

			string userName = Request.Form["UserName"];
			string pwdAnswer = Request.Form["PasswordAnswer"];

			OnBeforeResetPassword( userName, pwdAnswer );

			MembershipUser user = dotNetMembership.GetUser( userName );
			string pwd = user.ResetPassword( pwdAnswer );

			OnAfterResetPassword( user.Email, userName, pwdAnswer );

			return View( "ResetPassword", pwd as object );

		}

		#endregion

	}

	#region ViewData models.

	public class MembershipRoleViewData
	{
		public string Name { get; set; }
		public List<MembershipUser> Users { get; set; }
	}

	public class MembershipUserAndRolesViewData
	{
		public bool RolesEnabled { get; set; }
		public bool RequiresQuestionAndAnswer { get; set; }
		public MembershipUser User { get; set; }
		public List<string> AllRoles { get; set; }
		public List<string> UsersRoles { get; set; }
	}

	#endregion

}
